//
//  TFURLSessionManager.m
//  AFN_Test
//
//  Created by Guo Rui on 2017/9/11.
//  Copyright © 2017年 GR Harry. All rights reserved.
//

#import "TFURLSessionManager.h"

static NSString * const TFURLSessionManagerLockName = @"com.timeface.session.manager.lock";
static NSString * const TFURLSessionTaskDidResumeNotification = @"com.timeface.networking.session.task.resume";
static NSString * const TFURLSessionTaskDidSuspendNotification = @"com.timeface.networking.session.task.suspend";

@interface TFURLSessionManager ()<NSURLSessionDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSURLSessionTaskDelegate>
@property (strong, nonatomic) NSURLSession *session;
@property (strong, nonatomic) NSURLSessionConfiguration *configuration;
@property (strong, nonatomic) NSMutableDictionary *mutableTaskDelegateKeyedByTaskIndentifier;
@property (strong, nonatomic) NSLock *lock;
@property (copy, nonatomic, readonly) NSString *taskDescriptionForSessionTasks;

@end

@implementation TFURLSessionManager

- (TFURLSessionManager *)initWithConfiguration:(NSURLSessionConfiguration *)configuration
{
    if (self = [super init]) {
        
        // 1.
        self.responseSerializer = [AFJSONResponseSerializer serializer];
        
        // 2.
        self.reachabilityManager = [AFNetworkReachabilityManager manager];
        
        // 3. lock
        self.lock = [[NSLock alloc] init];
        self.lock.name = TFURLSessionManagerLockName;
        
        // 4.
        self.mutableTaskDelegateKeyedByTaskIndentifier = [NSMutableDictionary dictionaryWithCapacity:0];
        
        // 5. 配置NSURLSession
        self.operationQueue = [[NSOperationQueue alloc] init];
        self.operationQueue.maxConcurrentOperationCount = 1;
        
        if (nil == configuration) {
            self.configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
        }
        self.session = [NSURLSession sessionWithConfiguration:self.configuration delegate:self delegateQueue:self.operationQueue];
        
        // 4. 绑定NSURLSession中的所有task
        [self.session getTasksWithCompletionHandler:^(NSArray<NSURLSessionDataTask *> * _Nonnull dataTasks, NSArray<NSURLSessionUploadTask *> * _Nonnull uploadTasks, NSArray<NSURLSessionDownloadTask *> * _Nonnull downloadTasks) {
            for (NSURLSessionDataTask *dataTask in dataTasks) {
                [self addDelegaterForDataTask:dataTask uploadProgress:nil downloadProgress:nil completionHandler:nil];
            }
            
            for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
                [self addDelegaterForUploadTask:uploadTask uploadProgress:nil completionHandler:nil];
            }
            
            for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
                [self addDelegaterForDownloadTask:downloadTask downloadProgress:nil destination:nil completionHandler:nil];
            }
        }];
    }
    return self;
}

#pragma mark -
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    return [self dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:completionHandler];
}

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                              uploadProgress:(void (^)(NSProgress *))uploadProgress
                             downloadProgress:(void (^)(NSProgress *))downloadProgress
                            completionHandler:(void (^)(NSURLResponse *, id _Nullable, NSError * _Nullable))completionHandler
{
    NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];
    [self addDelegaterForDataTask:dataTask uploadProgress:uploadProgress downloadProgress:downloadProgress completionHandler:completionHandler];
    return dataTask;
}

#pragma mark -
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request progress:(void (^)(NSProgress *))downloadProgressBlock destination:(NSURL *(^)(NSURL *, NSURLResponse *))destination completionHandler:(void (^)(NSURLResponse *, NSURL *, NSError *))completionHandler
{
    NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithRequest:request];
    [self addDelegaterForDownloadTask:downloadTask downloadProgress:downloadProgressBlock destination:destination completionHandler:completionHandler];
    return downloadTask;
}


- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData progress:(void (^)(NSProgress *))downloadProgressBlock destination:(NSURL *(^)(NSURL *, NSURLResponse *))destination completionHandler:(void (^)(NSURLResponse *, NSURL *, NSError *))completionHandler
{
    NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithResumeData:resumeData];
    [self addDelegaterForDownloadTask:downloadTask downloadProgress:downloadProgressBlock destination:destination completionHandler:completionHandler];
    return downloadTask;
}


#pragma mark - 
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData uploadProgress:(void (^)(NSProgress *))uploadProgress completionHandler:(void (^)(NSURLResponse *, NSURL *, NSError *))completionHandler
{
    NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData];
    [self addDelegaterForUploadTask:uploadTask uploadProgress:uploadProgress completionHandler:completionHandler];
    return uploadTask;
}

- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL uploadProgress:(void (^)(NSProgress *))uploadProgress completionHandler:(void (^)(NSURLResponse *, NSURL *, NSError *))completionHandler
{
    NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL];
    [self addDelegaterForUploadTask:uploadTask uploadProgress:uploadProgress completionHandler:completionHandler];
    return uploadTask;
}

#pragma mark - NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    TFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
    
    if (delegate) {
        [delegate URLSession:session task:task didCompleteWithError:error];
        [self removeDelegateForTask:task];
    }
    
    if (self.taskDidComplete) {
        self.taskDidComplete(session, task, error);
    }
}

#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
    NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
    
    if (self.didReciveResponse) {
        disposition = self.didReciveResponse(session, dataTask, response);
    }
    
    if (completionHandler) {
        completionHandler(disposition);
    }
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    TFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    
    if (delegate) {
        [delegate URLSession:session dataTask:dataTask didReceiveData:data];
    }
    
    if (self.dataTaskDidReciveData) {
        self.dataTaskDidReciveData(session, dataTask, data);
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionUploadTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
    int64_t totalUnitBytes = totalBytesExpectedToSend;
    if (totalUnitBytes == NSURLSessionTransferSizeUnknown) {
        NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"];
        if (contentLength) {
            totalUnitBytes = (int64_t)[contentLength longLongValue];
        }
    }
    
    TFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
    
    if (delegate) {
        [delegate URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalUnitBytes];
    }
    
    if (self.uploadTaskDidSendBodyData) {
        self.uploadTaskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitBytes);
    }
}

#pragma mark - NSURLSessionDownloadTaskDelegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    TFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite];
    }
    
    if (self.downloadTaskDidWriteData) {
        self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
    }
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
    TFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didResumeAtOffset:fileOffset expectedTotalBytes:expectedTotalBytes];
    }
    
    if (self.downloadTaskDidResume) {
        self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
    }
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
    TFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    
    if (self.downloadTaskDidFinishDownloading) {
        NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        
        if (fileURL) {
            delegate.downloadFileURL = fileURL;
            NSError *error = nil;
            
            if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error]) {
                [[NSNotificationCenter defaultCenter] postNotificationName:TFURLSessionTaskDidFailToMoveFileNotifcation object:downloadTask userInfo:error.userInfo];
            }
        }
        
        return;
    }
    
    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
    }
}

#pragma mark - Actions
- (void)taskDidResume:(NSNotification *)notification
{
    NSURLSessionTask *task = notification.object;
    if (nil != task && [task respondsToSelector:@selector(taskDescription)]) {
        if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:TFNetworkingTaskDidResumeNotifcation object:task];
            });
        }
    }
}

- (void)taskDidSuspend:(NSNotification *)notification
{
    NSURLSessionTask *task = notification.object;
    if (nil != task && [task respondsToSelector:@selector(taskDescription)]) {
        if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:TFNetworkingTaskDidSuspendNotifcation object:task];
            });
        }
    }
}

#pragma mark - Setter
- (void)setTaskDidComplete:(TFURLSessionTaskDidCompleteBlock)taskDidComplete
{
    if (taskDidComplete) {
        self.taskDidComplete = taskDidComplete;
    }
}

- (void)setDidReciveResponseBlock:(TFURLSessionDataTaskDidReciveResponseBlock)didReciveResponse
{
    if (didReciveResponse) {
        self.didReciveResponse = didReciveResponse;
    }
}

- (void)setDataTaskDidReciveData:(TFURLSessionDataTaskDidReciveDataBlock)dataTaskDidReciveData
{
    if (dataTaskDidReciveData) {
        self.dataTaskDidReciveData = dataTaskDidReciveData;
    }
}

- (void)setDownloadTaskDidWriteData:(TFURLSessionDownloadTaskDidWriteDataBlock)downloadTaskDidWriteData
{
    if (self.downloadTaskDidWriteData) {
        self.downloadTaskDidWriteData = downloadTaskDidWriteData;
    }
}

- (void)setDownloadTaskDidResume:(TFURLSessionDownloadTaskDidResumeBlock)downloadTaskDidResume
{
    if (self.downloadTaskDidResume) {
        self.downloadTaskDidResume = downloadTaskDidResume;
    }
}

- (void)setDownloadTaskDidFinishDownloading:(TFURLSessionDownloadTaskDidFinishDownloadingBlock)downloadTaskDidFinishDownloading
{
    if (self.downloadTaskDidFinishDownloading) {
        self.downloadTaskDidFinishDownloading = downloadTaskDidFinishDownloading;
    }
}

- (void)setUploadTaskDidSendBodyData:(TFURLSessionUploadTaskDidSendBodyDataBlock)uploadTaskDidSendBodyData
{
    if (self.uploadTaskDidSendBodyData) {
        self.uploadTaskDidSendBodyData = uploadTaskDidSendBodyData;
    }
}

#pragma mark - Private
- (TFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task
{
    TFURLSessionManagerTaskDelegate *delegate = nil;
    [self.lock lock];
    delegate = self.mutableTaskDelegateKeyedByTaskIndentifier[@(task.taskIdentifier)];
    [self.lock unlock];
    
    return delegate;
}

- (void)setDelegater:(TFURLSessionManagerTaskDelegate *)delegater forTask:(NSURLSessionTask *)task
{
    NSParameterAssert(delegater);
    NSParameterAssert(task);
    
    [self.lock lock];
    self.mutableTaskDelegateKeyedByTaskIndentifier[@(task.taskIdentifier)] = delegater;
    [self addNotficationObserverForTask:task];
    [self.lock unlock];
}

- (void)removeDelegateForTask:(NSURLSessionTask *)task
{
    NSParameterAssert(task);
    
    [self.lock lock];
    [self.mutableTaskDelegateKeyedByTaskIndentifier removeObjectForKey:@(task.taskIdentifier)];
    [self removeNotificationObserverFor:task];
    [self.lock unlock];
}

- (void)addNotficationObserverForTask:(NSURLSessionTask *)task
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:TFURLSessionTaskDidResumeNotification object:task];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:TFURLSessionTaskDidSuspendNotification object:task];
}

- (void)removeNotificationObserverFor:(NSURLSessionTask *)task
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:TFURLSessionTaskDidResumeNotification object:task];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:TFURLSessionTaskDidSuspendNotification object:task];
}

- (void)addDelegaterForDataTask:(NSURLSessionDataTask *)dataTask
                 uploadProgress:(void (^)(NSProgress *))uploadProgress
               downloadProgress:(void (^)(NSProgress *))downloadProgress
              completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
    // 1. 创建delegate
    TFURLSessionManagerTaskDelegate *delegate = [[TFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;
    delegate.uploadProgressBlock = uploadProgress;
    delegate.downloadProgressBlock = downloadProgress;
    
    // 2. 绑定dataTask 和 当前类
    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    
    // 3. 存储dataTask 和 delegate 的关系链
    [self setDelegater:delegate forTask:dataTask];
}

- (void)addDelegaterForUploadTask:(NSURLSessionUploadTask *)uploadTask
                   uploadProgress:(void (^)(NSProgress *))uploadProgress
                completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
    // 1. 创建Delegate
    TFURLSessionManagerTaskDelegate *delegate = [[TFURLSessionManagerTaskDelegate alloc] initWithTask:uploadTask];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;
    delegate.uploadProgressBlock = uploadProgress;
    
    // 2. 绑定uploadTask 和 当前类
    uploadTask.taskDescription = self.taskDescriptionForSessionTasks;
    
    // 3. 存储uploadTask 和 delegate 的关系链
    [self setDelegater:delegate forTask:uploadTask];
}

- (void)addDelegaterForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
                   downloadProgress:(void (^)(NSProgress *))downloadProgress
                        destination:(NSURL *(^)(NSURL *targetURL, NSURLResponse *response))destination
                  completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
    // 1. 创建delegate
    TFURLSessionManagerTaskDelegate *delegate = [[TFURLSessionManagerTaskDelegate alloc] initWithTask:downloadTask];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;
    delegate.downloadProgressBlock = downloadProgress;
    
    if (destination) {
        delegate.downloadTaskDidFinishDownloading = ^NSURL *(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location) {
            return destination(location, downloadTask.response);
        };
    }
    
    // 2. 绑定downloadTask 和 当前类
    downloadTask.taskDescription = self.taskDescriptionForSessionTasks;
    
    // 3. 存储downloadTask 和 delegate 的关系链
    [self setDelegater:delegate forTask:downloadTask];
}



- (NSString *)taskDescriptionForSessionTasks
{
    return [NSString stringWithFormat:@"%p", self];
}

@end
